package frontend;

import java.util.*;
import daytrader.common.Utility;

public class SemanticAnalyzer {
	public static CompileErrors errs = new CompileErrors();
	public static SymbolTable rootST = new SymbolTable();
	public static SymbolTable currentST = rootST; // current symbol table

        private String dataPathFileName;

	public SemanticAnalyzer(String dataPathFileName) {
	        this.dataPathFileName = dataPathFileName;
	}

	public void analyze(SyntaxTreeNode root) throws CompileErrors {
		/* Init symbol Table */
		rootST.init(root);
		Iterator it = Utility.enumerateStockNames(dataPathFileName);
		while (it.hasNext()) {
		   rootST.stocknameTable.put((String) it.next(), 1);
                }
		
		/* start type Semantic analysis */
		int ProgNum = root.getChildrenCount(); // Determine if buy, buy/sell or with user_defined
		if(ProgNum == 1){ // only buy
			SyntaxTreeNode firNode = root.getChild(0);
			for(SyntaxTreeNode child : firNode.getChildren()){
				analyzeStrategy(child,SyntaxTreeNode.Type.BUYSTRAT);
			}
		}
		else if(ProgNum == 2){
			SyntaxTreeNode firNode = root.getChild(0);
			for(SyntaxTreeNode child : firNode.getChildren()){
				analyzeStrategy(child,SyntaxTreeNode.Type.BUYSTRAT);
			}
			SyntaxTreeNode secNode = root.getChild(1);
			if(secNode.getType()== SyntaxTreeNode.Type.SELLSTRAT){ // not user_defined
				for(SyntaxTreeNode child : secNode.getChildren()){
					analyzeStrategy(child,SyntaxTreeNode.Type.SELLSTRAT);
				}
			}
			else
				analyzeUserDefined(secNode);
		}
		else if(ProgNum == 3){
			SyntaxTreeNode firNode = root.getChild(0);
			for(SyntaxTreeNode child : firNode.getChildren()){
				analyzeStrategy(child,SyntaxTreeNode.Type.BUYSTRAT);
			}			
			SyntaxTreeNode secNode = root.getChild(1);
			for(SyntaxTreeNode child : secNode.getChildren()){
				analyzeStrategy(child,SyntaxTreeNode.Type.SELLSTRAT);
			}
			SyntaxTreeNode thiNode = root.getChild(2);			
			analyzeUserDefined(thiNode);
		}
		if (errs.size() > 0)
			throw errs;
		
		//System.exit(0);
	}
	public void analyzeUserDefined(SyntaxTreeNode node) {
		/* Examine this node*/
		SyntaxTreeNode.Type type = node.getType();
		
		switch(type) {
			case ASSIGN:
				examineAssign(node);
				return;
			case ARGUMENT:
				examineArgument(node);
				return;
			case SIGNATURE:
				examineHasReturn(node);
				break;
			case LOGICAL:
			case OP:
				examineOperator(node);
				return;
			case RETURN:
				examineReturn(node);
				return;		
			case IFGROUP:
				// do nothing
				break;
			case USERFUNC:
			case FOR:
			case WHILE:
			case IF:
				nestNewSymbolTable();
				break;
			case ELSEIF:
			case ELSE:
				// Check must follow if or else if
				examineIfElse(node);
				nestNewSymbolTable();
				break;
			default:
				break;
		}
		
		if(node.getChildrenCount() > 0){
			for(SyntaxTreeNode child : node.getChildren()){
				analyzeUserDefined(child);
			}
		}
		
		/* Check for scope end */
		switch(type) {
		case FOR:
		case WHILE:
		case IF:
		case ELSEIF:
		case ELSE:
		case USERFUNC:
			/* Check must follow if or else if */
			//rootST.print_ids("");
			disposeSymbolTable();
			break;
		default:
			break;
		}
		
		return;
	}
	public void analyzeStrategy(SyntaxTreeNode node, SyntaxTreeNode.Type strat){
		SyntaxTreeNode.Type type = node.getType();
		switch(type) {
		case SELECT:
			examineSelect(node);
			break;
		case LOGICAL:
			examineLogical(node);
			break;
		case PRINT:
			examinePrint(node);
			break;
		case CROSSOP:
			examineCrossOP(node);
			break;
		case BUY: 
			if(strat == SyntaxTreeNode.Type.SELLSTRAT){
				errs.addError(new CompileError("ActionError: buy action in sell program.", node.getLineNumber()));			
			}
			break;
		case SELL:
			if(strat == SyntaxTreeNode.Type.BUYSTRAT){
				errs.addError(new CompileError("ActionError: sell action in buy program.", node.getLineNumber()));			
			}
			break;
		case OP:
			examineOP(node);
			break;
		case TF:
			break;
		default:
			//System.out.println("ERROR TYPE IN STRATEGY: "+ type.toString());
			break;
		}
	}
	
	/*any user function argument type cannot be void
	 *and not any two argument variable name share a name  
	 * */
	private void examineArgument(SyntaxTreeNode argument) {
		
		/*check the correct type*/
		SyntaxTreeNode typeNode = argument.getChild(0);
		if (typeNode.getStringValue().equals("void"))
			errs.addError( "function argument \"" + argument.getStringValue()
					+"\" :type cannot be decleared as \"void\"",
					argument.getLineNumber());
		
		String var_name = argument.getStringValue();
		/*check the variable name*/
		if (currentST.existVariable(var_name))
			errs.addError( "function argument \"" + argument.getStringValue()
					+"\" :symbol already exiseted!",
					argument.getLineNumber());
		else
			{
			String var_type = SyntaxTreeNodeUtil.getNodeValueType(argument);
			currentST.addVariable(var_name,var_type);
			}
		return;
	}
		
	private void examineAssign(SyntaxTreeNode node) {
		String op_str = node.getStringValue();
		SyntaxTreeNode id = node.getFirstChild();
		String var_name = node.getFirstChild().getStringValue();
		String var_type = "";

		/* Handle cascading assignment - "a = b = 3" */
		if (op_str.equals("=")) {
			SyntaxTreeNode rightChild = node.getChild(1);
			if (rightChild.getType() == SyntaxTreeNode.Type.ASSIGN
					&& rightChild.getStringValue().equals("=")) {
				// if it is an cascading assignment, recursively call to
				// its right child
				examineAssign(rightChild);
			}
		}

		/* check i++, i--, i must be defined already */
		if (op_str.equals("++") || op_str.equals("--")) {
			//Utility.debug(var_name + op_str);
			
			if (!currentST.existVariable(var_name)) {
				errs.addError("'" + var_name + "' is not defined.",
						node.getLineNumber());
				return;
			} else {
				var_type = currentST.getVariableType(var_name);
				// Make sure id is arithmetic type
				if (!SyntaxTreeNodeUtil.isNumericalType(var_type)) {
					errs.addError(
							"++ or -- can only be applied to int or float",
							node.getLineNumber());
					return;
				}
			}
			
		}

		/*
		 * If the variable is already there in the symbol table, check whether
		 * the assignment assign a value of different type to it. If so, report
		 * error
		 */
		if (currentST.existVariable(var_name) 
				&& !op_str.equals("++") && !op_str.equals("--")) {
			var_type = currentST.getVariableType(var_name);
			//Utility.debug("found in symbol table ", var_name, "=", var_type);
			String right_type = SyntaxTreeNodeUtil.getNodeValueType(node
					.getChild(1));
			if (!SyntaxTreeNodeUtil.isTypeCompatible(var_type, right_type)) {
				errs.addError(
						"Variable type cannot be changed to incompatible type "
								+ "once being defined: "
								+ "trying to assign type '" + right_type
								+ "' to '" + var_type + "',",
						node.getLineNumber());
				return;
			}
		}

		/*
		 * Check right side of '=' is: expression | assignment
		 */
		if (op_str.equals("=")) {
			SyntaxTreeNode rightChild = node.getChild(1);
			SyntaxTreeNode.Type rnode_type = rightChild.getType();
			
			if (!SyntaxTreeNodeUtil.isDerivableFromExpression(rnode_type)
					&& rnode_type != SyntaxTreeNode.Type.ASSIGN) {
				errs.addError("Right operand of '=' is not valid", 
						node.getLineNumber());
				return;
			}
		}
		
		/* Check right side of += is:  expression */
		if (op_str.equals("+=") 
				|| op_str.equals("-=") 
				|| op_str.equals("*=") 
				|| op_str.equals("/=")
				|| op_str.equals("^=")) {
			SyntaxTreeNode rightChild = node.getChild(1);
			SyntaxTreeNode.Type rnode_type = rightChild.getType();
			
			
			if (!SyntaxTreeNodeUtil.isDerivableFromExpression(rnode_type)) {
				errs.addError("Right operand of '" + 
						op_str + "' is not valid", 
						node.getLineNumber());
				return;
			}
		}
		
		/* Get variable type */
		if (!currentST.existVariable(var_name)) {
			SyntaxTreeNode rnode = node.getChild(1);
			var_type = SyntaxTreeNodeUtil.getNodeValueType(rnode);
			if (var_type.equals("void")) {
				errs.addError( "Variable initialization error: \""+var_name+"\" cannot be \"void\"",
						node.getLineNumber());
				return;
			}
				
			
		} else {
			var_type = currentST.getVariableType(var_name);
		}
			
		/* Add variable to symbol table if not already exists */
		if (!currentST.existVariable(var_name)) {
			currentST.addVariable(var_name, var_type);
			node.setFirstAssign(true);
			id.setFirstAssign(true);
			//Utility.debug("Add new assignment to symbol table:", var_name, "=",
			//	var_type);
		}
		
		/* Set node info */
		id.setValueType(var_type);
		node.setValueType(var_type);
		
	}

	private void examineOperator(SyntaxTreeNode node) {
		SyntaxTreeNodeUtil.getNodeValueType(node);
	}
	private void examinePrint(SyntaxTreeNode node) {
		SyntaxTreeNodeUtil.getNodeValueType(node);
	}
	private void examineCrossOP(SyntaxTreeNode node) {
		SyntaxTreeNodeUtil.getNodeValueType(node);
	}	
	private void examineOP(SyntaxTreeNode node) {
		SyntaxTreeNodeUtil.getNodeValueType(node);
	}	

	private void examineLogical(SyntaxTreeNode node){
		SyntaxTreeNodeUtil.getNodeValueType(node);
	}
	
	private void checkSigRet(SyntaxTreeNode returnNode ,String returnType) {
		SyntaxTreeNode parent = returnNode.getParent();
		while(parent.getType()!=SyntaxTreeNode.Type.USERFUNC)
			parent = parent.getParent();
		SyntaxTreeNode sigNode = parent.getFirstChild();
		String sigReturnType = sigNode.getFirstChild().getStringValue();
		
		if (!SyntaxTreeNodeUtil.isTypeCompatible(sigReturnType, returnType)) {
			errs.addError( "Return type not consistant:\"" + sigReturnType
					+"\" expected!",
					returnNode.getLineNumber());
	        }
	}
	private void examineReturn(SyntaxTreeNode returnNode){
		
		String returnType = null;
		
		if (returnNode.getChildrenCount()>0) {
			/*has expression after return, therefore not void*/
			SyntaxTreeNode retExprNode = returnNode.getFirstChild();
			returnType = SyntaxTreeNodeUtil.getNodeValueType(retExprNode);
			}
		else
			/*return void*/
			returnType = "void";
		returnNode.setValueType(returnType);
		checkSigRet(returnNode,returnType);
		checkRemainingUnreach(returnNode);
	}
	
	private void examineSelect(SyntaxTreeNode node) {
		if(node.getValue() == "SELECT ALL")
			return;

		// examine if the list contains only the String Literal
		ArrayList<SyntaxTreeNode> childList = node.getChildren();
		SyntaxTreeNode List = childList.get(0);
		for(SyntaxTreeNode child : List.getChildren()) {
			if (child.getType() ==  SyntaxTreeNode.Type.LITSTRING){
				if(rootST.checkStockname(child.getValue().toString()))
					continue;
				else{
					errs.addError(new CompileError("NameError: no stock names with " +child.getValue().toString() + ".", child.getLineNumber()));
					continue;
				}
			}
			errs.addError(new CompileError("TypeError: select should be followed by 'all' or a list of stock names.", child.getLineNumber()));
		}
		return;
	}
	
	private void examineIfElse(SyntaxTreeNode node) {
		// Check previous sibling can only be 'ELSEIF' or 'IF'
		Boolean correct_prev = true;
		SyntaxTreeNode prev_sib = node.getPrevSibling();
		if (prev_sib == null) {
			correct_prev = false;
		} else {
			SyntaxTreeNode.Type prev_type = prev_sib.getType();
			if (prev_type == SyntaxTreeNode.Type.IFGROUP) 
				prev_type = prev_sib.getLastChild().getType();

			if (prev_type != SyntaxTreeNode.Type.IF &&
					prev_type != SyntaxTreeNode.Type.ELSEIF) {
				correct_prev = false;
			}
		}
		
		if (!correct_prev) {
			errs.addError("'else if' and 'else' must follow a 'if' or 'else if' block", 
					node.getLineNumber());
		}
	}
	
	private void nestNewSymbolTable(){
		currentST.createNestSymbolTable();
		currentST = currentST.child;
	}
	
	private void disposeSymbolTable() {
		currentST = currentST.parent;
		currentST.removeNestSymbolTable();
	}
	
	
	private void checkRemainingUnreach(SyntaxTreeNode returnNode) {
		if (hasSuccessor(returnNode))
				errs.addError("Unrechable code after \"return \" statement !",
						returnNode.getLineNumber());
	}
	
	/*this function checks if node has successor in a subtree
	 * rooted at two cases
	 * 1) the root is a IF ELSE ELSEIF
	 * 2) the root is the func root node
	 * */
	private boolean hasSuccessor(SyntaxTreeNode returnNode) {
		
		if (returnNode.hasNextSibling())
			return true;
		
		else {
			SyntaxTreeNode currNode = returnNode;
			while (!currNode.isBranchNode()&&!currNode.isFuncRootNode()){
					if (currNode.hasNextSibling())
						return true;
					currNode =currNode.getParent();
			}
			return false;
		}
	}
	
	/*check the function has at least one return statement*/
	private boolean hasReturn(SyntaxTreeNode node){
		if (node.getType()==SyntaxTreeNode.Type.RETURN)
			return true;
		ArrayList<SyntaxTreeNode> childlist =  node.getChildren();
		for (SyntaxTreeNode child : childlist)
			if (hasReturn(child))
				return true;
		return false;
	}
	private void examineHasReturn(SyntaxTreeNode signature) {
		SyntaxTreeNode userFunc = signature.getParent();
		if (!SyntaxTreeNodeUtil.getUserFuncReturnType(userFunc).equals("void"))
			if (!hasReturn(userFunc))
				errs.addError("Function \""+signature.getStringValue()+"\" return statement missing!",
					userFunc.getLineNumber());
	}
	
}
